using System;
using System.Collections.Generic;
using System.Reflection;
using DependencyAnalysis.DataModel;

namespace DependencyAnalysis
{
    public interface IVerifier
    {
        /// <summary>
        /// Raise a DependencyException when foreign modules are used other than the specified ones.
        /// If one of the allowed foreign modules is not use at all, raise an exception as well.
        /// </summary>
        /// <param name="allowedForeigners">collection of the full names of allowed references, e.g. theeg.DependencyAnalysis</param>
        void VerifyForeignModules(IEnumerable<string> allowedForeigners);

        /// <summary>
        /// Raise a DependencyException when foreign modules are used other than the specified ones.
        /// If one of the allowed foreign modules is not use at all, raise an exception as well.
        /// </summary>
        /// <param name="allowedForeigners">collection of the allowed foreign assemblies</param>
        void VerifyForeignModules(IEnumerable<Assembly> allowedForeigners);

        /// <summary>
        /// Modules of the .net framework are defined that must be referred to by the project. Other framework
        /// modules are not allowed.
        /// </summary>
        /// <param name="allowedFrameworkModules">a collection of allowed framework modules</param>
        void VerifyFrameworkModules(IEnumerable<Assembly> allowedFrameworkModules);

        /// <summary>
        /// Modules of the .net framework are defined that must be referred to by the project. Other framework
        /// modules are not allowed.
        /// </summary>
        /// <param name="allowedFrameworkModules">collection of the full names of allowed references, e.g. System.IO</param>
        void VerifyFrameworkModules(IEnumerable<string> allowedFrameworkModules);

        /// <summary>
        /// Specify which submodule are approved to be part of the module.
        /// </summary>
        /// <param name="module"></param>
        /// <param name="allowedSubmodules"></param>
        void VerifySubmodulesInModule(ModuleDescriptor module, IEnumerable<SubmoduleDescriptor> allowedSubmodules);

        /// <summary>
        /// Raise a DependencyException when the referrer accesss the target on another abstraction level than
        /// the allowed levels. You often want to ensure that a component is accessed via interface only.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="referrer">member, type, submodule or module</param>
        /// <param name="target">descriptor for member, type, submodule or module</param>
        /// <param name="abstractionType">the allowed access types</param>
        void VerifyAccessType<T>(IDescriptor referrer, T target, AbstractionType abstractionType) where T : class,IDescriptor;

        /// <summary>
        /// Qualify whether referrer type depends on target type.
        /// </summary>
        /// <param name="referrer"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        bool RefersTo(Type referrer, Type target);

        /// <summary>
        /// Qualify whether referrer assembly depends on target assembly.
        /// </summary>
        /// <param name="referrer"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        bool RefersTo(Assembly referrer, Assembly target);

        /// <summary>
        /// Qualify whether referrer depends on target. Both referrer and target can be of the types
        /// MemberDescriptor, TypeDescriptor, SubmoduleDescriptor or ModuleDescriptor. The type of
        /// referrer can be different from targets type. For example TypeDescriptor(UnitTests.AnalyzerTest)
        /// depends on ModuleDescriptor(DependencyAnalysis).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="referrer"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        bool RefersTo<T>(IDescriptor referrer, T target) where T : class, IDescriptor;

        /// <summary>
        /// Raise a DependencyException when referrer accesses target. The exception reports all types
        /// that have forbidden references. Both referrer and target can be of the types
        /// MemberDescriptor, TypeDescriptor, SubmoduleDescriptor or ModuleDescriptor. The type of
        /// referrer can be different from targets type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="referrer"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        void VerifyDoesNotReferTo<T>(IDescriptor referrer, T target) where T : class, IDescriptor;

        /// <summary>
        /// Raise an exception when any type of your project is not used by at least two other types in the project.
        /// Those types are candidates for being removed as they are often just used state objects 
        /// for one single type.
        /// You may define exceptions where you know that this fact is intended.
        /// </summary>
        /// <param name="allowedExceptions">a list of types, submodules and modules</param>
        void VerifyVersatilityForProject(IEnumerable<IDescriptor> allowedExceptions);

        /// <summary>
        /// Raise an exception when the type is not used by at least two other types in the project.
        /// Those types are candidates for being removed as they are often just used state objects 
        /// for one single type.
        /// </summary>
        /// <param name="type"></param>
        void VerifyVersatility(TypeDescriptor type);

        /// <summary>
        /// Raise a DependencyException when any member or type accesses the target, when the referrer
        /// is not covered by the list of allowed referrers.
        /// 
        /// For example you have implemented a creational pattern and you want no other type to call the 
        /// constructor for created types other than the builder or factory.
        /// 
        /// You have a wrapped some functionality of external modules and your own code shall only call
        /// to the wrappers (decouple from foreign code). Then you are enabled to exchange the external
        /// modules without adapting large portions of your code.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="allowedReferrers">any combination of descriptors for member, type, submodule, module</param>
        /// <param name="target">member, type, submodule or module</param>
        void VerifyNoOtherReferrers<T>(IEnumerable<IDescriptor> allowedReferrers, T target) where T : class,IDescriptor;

        /// <summary>
        /// Raise a DependencyException when a member inside the given scope is not referenced. The validation
        /// can be delimited to defined visibilities.
        /// </summary>
        /// <param name="target">the search scope can be member, type, submodule or module</param>
        /// <param name="visibility">visibilities can be bitwise combined, choose ignore to select all</param>
        void VerifyMembersAreUsed(IDescriptor target, NodeVisibility visibility);

        /// <summary>
        /// An assumption is checked for all instances of the specified descriptor class in the dependency model.
        /// When there are any instances that do not fit to the assumption a DependencyExcption is raised,
        /// reporting all failing instances.
        /// Use it for example to combine metrics.
        /// </summary>
        /// <typeparam name="T">the dscriptors to check</typeparam>
        /// <param name="assumption">return false, when an instance shall not pass the test</param>
        void VerifyThat<T>(Func<T, State, bool> assumption) where T : IDescriptor;

        /// <summary>
        /// Check a custom metric for all instances of the specified descriptor class in the dependency
        /// model. Return the metrics result without throwing a DependencyException.
        /// It enables you to create reports to be used for visualizations.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="metric"></param>
        /// <returns></returns>
        IList<Tuple<T, State>> Report<T>(Func<T, State, bool> metric) where T : IDescriptor;

        /// <summary>
        /// An assumption is checked for all instances of the specified descriptor class in the dependency model.
        /// When there are any instances that do not fit to the assumption a DependencyExcption is raised,
        /// reporting all failing instances.
        /// Use it for example to combine metrics.
        /// With the where clause you can delimit the scope, e.g. to your own code or to a single module.
        /// </summary>
        /// <typeparam name="T">the dscriptors to check</typeparam>
        /// <param name="assumption">return false, when an instance shall not pass the test</param>
        /// <param name="whereClause">scope delimiter</param>
        void VerifyThat<T>(Func<T, State, bool> assumption, Predicate<T> whereClause) where T : IDescriptor;

        /// <summary>
        /// Check a custom metric for all instances of the specified descriptor class in the dependency
        /// model. Return the metrics result without throwing a DependencyException.
        /// It enables you to create reports to be used for visualizations.
        /// With the where clause you can delimit the scope, e.g. to your own code or to a single module.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="metric"></param>
        /// <param name="whereClause">scope delimiter</param>
        /// <returns></returns>
        IList<Tuple<T, State>> Report<T>(Func<T, State, bool> metric, Predicate<T> whereClause) where T : IDescriptor;
    }
}